<?php
namespace Tbf\Util;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\PhpProcess;
use Tbf\Util\Pipe;
use Tbf\Serialize\SerializeUtil;
/**
 * 在另一个进程里面运行
 * */
class ProcessUtil
{
    /**
     * 运行bash命令,
     * 并返回标准输出里面的东西,
     * 如果命令运行失败,产生异常
     * 需要更高级功能请使用Symfony\Component\Process\Process
     * */
    public static function runCmd($command,$cwd=null)
    {
        $process = new Process($command, $cwd);
        $process->run();
        $code = $process->getExitCode();
        if ($code!=0) {
            throw new \Exception(
                sprintf("run command line fail! \n".
                    "command:%s\n".
                    "code:%s\n".
                    "errorOutput:%s\n"
                    ,$command,
                    $code,
                    $process->getErrorOutput())
            );
        }
        $output = $process->getOutput();

        return $output;
    }
    /**
     * 在新进程里面运行php脚本.
     * 并返回标准输出里面的东西
     * */
    public static function runPhpInNew($php_source)
    {
        $process = new PhpProcess($php_source);
        $process->run();
        $output = $process->getOutput();

        return $output;
    }
    /**
     * 在本进程里面fork出一个新进程并执行callback
     * 然后退出.当前进程等待新进程执行完毕
     * 返回callback的返回值
     * 注意:
     * 1.此处使用pipeUtil和pcntl_fork
     * 2.新进程可以向当前的控制台里面写东西
     * 3.用闭包从新进程数据回到调用者没有效果
     * 4.返回值会被序列化和反序列化
     * 5.callback里面的异常会显示出来但是不会调用者不会发现有异常
     * */
    public static function runPhpInFork($cb)
    {
        $pipe = PipeUtil::getPipe();
        $pid = pcntl_fork();
        switch ($pid) {
            case -1:
                throw new \Exception('can not create new process');
            case 0:
                $pipe->reader->close();
                $ret = null;
                $exception = null;
                try {
                    $ret = $cb();
                } catch (\Exception $exception) {
                }
                $toWrite = array(
                        'return'=>$ret,
                        'exception'=>$exception
                    );
                //异常对象里面会包含一个闭包,而闭包是不能serialize的
                $toWrite = SerializeUtil::safePhpSerialize($toWrite);
                $pipe->writer->write($toWrite);
                $pipe->close();
                exit();
                break;
            default:
                $pipe->writer->close();
                $ret = $pipe->read();
                if (empty($ret)) {
                    throw new \Exception(
                        'exit unexpected in runPhpInFork,maybe fatal error or call exit()');
                }
                if ($ret['exception']===null) {
                    $to_ret = $ret['return'];
                } else {
                    throw $ret['exception'];
                }
                $pipe->close();

                return $to_ret;
                break;
        }
    }
}
